package cn.shengongying.service.impl;

import cn.shengongying.anon.PayStrategy;
import cn.shengongying.enums.CacheKeyEnum;
import cn.shengongying.enums.PayEnum;
import cn.shengongying.enums.PositionStatusEnum;
import cn.shengongying.exception.BusinessException;
import cn.shengongying.model.BossOrder;
import cn.shengongying.model.ConfingWxPay;
import cn.shengongying.model.PayResult;
import cn.shengongying.service.IBossOrderService;
import cn.shengongying.service.IBossPositionService;
import cn.shengongying.service.IUserDiscountInfoService;
import cn.shengongying.utils.DateUtils;
import cn.shengongying.utils.DecryptUtils;
import com.alibaba.fastjson.JSONObject;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConfig;
import com.github.wxpay.sdk.WXPayUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.*;

/**
 * author: RXK
 * date: 2022/1/10 星期一
 * desc: 微信支付
 */
@Service
@PayStrategy
public class WechatPayStrategyServiceImpl extends AbstractPayStrategyServiceService {


    private final IBossOrderService bossOrderService;

    private final IBossPositionService bossPositionService;

    private final RedisTemplate<String, String> redisTemplate;

    private final IUserDiscountInfoService userDiscountInfoService;


    private static final String returnCode = "return_code";

    private static final String resultCode = "result_code";

    private static final String errCode = "err_code";

    private static final String errCodeDes = "err_code_des";


    private WXPay WX_PAY;

    public WechatPayStrategyServiceImpl(IBossOrderService bossOrderService, IBossPositionService bossPositionService, RedisTemplate<String, String> redisTemplate, IUserDiscountInfoService userDiscountInfoService) {
        this.bossOrderService = bossOrderService;
        this.bossPositionService = bossPositionService;
        this.redisTemplate = redisTemplate;
        this.userDiscountInfoService = userDiscountInfoService;
    }

    @Override
    public PayEnum supportType() {
        return PayEnum.wechat;
    }


    @Override
    protected Map<String, String> prepayParams(BossOrder bossOrder) throws BusinessException {
        try {
            WXPayConfig wxPayConfig = new ConfingWxPay();
            WX_PAY = new WXPay(wxPayConfig);
            return createParams(bossOrder);
        } catch (Exception e) {
            throw new BusinessException("预支付出现异常:{" + e.getMessage() + "}");
        }
    }

    @Override
    protected Map<String, String> prepay(Map<String, String> prepayParams) throws Exception {
        try {
            return WX_PAY.unifiedOrder(prepayParams);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException("支付失败");
        }
    }


    @Override
    protected PayResult prepayResult(Map<String, String> prepay, BossOrder bossOrder) throws Exception {
        PayResult payResult = new PayResult();
        verifyPrepayResult(prepay);
        Map<String, String> result = ConfingWxPay.prepayResult(prepay);
        payResult.setParams(result);
        bossOrderService.save(bossOrder);
        return payResult;
    }


    @Override
    protected void handleCallback(Map<String, String> callback) {
        String outTradeNo = callback.get("out_trade_no");
        String transactionId = callback.get("transaction_id");
        String timeEnd = callback.get("time_end");
        BossOrder bossOrder = bossOrderService.findByOrderNo(outTradeNo);
        if (bossOrder.getStatus() != 0) {
            return;
        }

        BossOrder update = new BossOrder();
        update.setTransactionId(transactionId);
        update.setId(bossOrder.getId());
        update.setPaySuccessTime(DateUtils.string2Date(timeEnd, DateUtils.date_time_ss));
        String s = callback.get(resultCode);
        if (s.equalsIgnoreCase("SUCCESS")) {
            update.setStatus(1);
            update.setSerialNumber(getSerialNumber());

            if (bossOrder.getDiscountId() != null) {
                userDiscountInfoService.updateStatus(bossOrder.getDiscountId(), 1);
            }

            bossPositionService.updateStatusById(bossOrder.getExtId(), PositionStatusEnum.recruitment.ordinal());
        } else {
            update.setStatus(2);
        }
        bossOrderService.updateById(update);
    }

    private String getSerialNumber() {
        String str = DateUtils.date2Str(new Date(), DateUtils.date);
        String serialNumber = fromCache();
        return str+serialNumber;
    }

    private String fromCache() {
        Long increment = redisTemplate.opsForValue()
                .increment(CacheKeyEnum.order_serial_number.getKey());
        if (increment != null && increment == 1) {
            redisTemplate.expire(CacheKeyEnum.order_serial_number.getKey(), DateUtils.endDateDuration());
        }
        return String.format("%05d",increment);
    }


    @Override
    protected void handleRefund(Map<String, String> data) {
        try {
            String reqInfo = data.get("req_info");
            String decrypt = DecryptUtils.wechatOrderDecrypt(reqInfo, ConfingWxPay.key);
            Map<String, String> refundData = WXPayUtil.xmlToMap(decrypt);
            String outTradeNo = refundData.get("out_trade_no");
            BossOrder bossOrder = bossOrderService.findByOrderNo(outTradeNo);
            if (bossOrder.getStatus() == 4) {
                BossOrder update = new BossOrder();
                update.setId(bossOrder.getId());
                String refundStatus = refundData.get("refund_status");
                if (refundStatus.equalsIgnoreCase("SUCCESS")) {
                    update.setStatus(5);
                } else {
                    update.setStatus(6);
                }
                String successTime = refundData.get("success_time");
                Date successDate = DateUtils.string2Date(successTime, DateUtils.date_time_minus_sec);
                update.setRefundSuccessTime(successDate);
                bossOrderService.updateById(update);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void orderRefund(BossOrder bossOrder) {
        ConfingWxPay confingWxPay = new ConfingWxPay();
        try {
            WXPay wxPay = new WXPay(confingWxPay);
            Map<String, String> refund = wxPay.refund(refundParams(bossOrder));
            if (refund.get(returnCode)
                    .equalsIgnoreCase("FAIL")) {
                bossOrder.setStatus(7);
            } else {
                //通信结果为正确的 业务结果不一定正确
                if (refund.get(resultCode)
                        .equalsIgnoreCase("FAIL")) {
                    bossOrder.setStatus(7);
                } else {
                    bossOrder.setStatus(4);
                    bossOrder.setRefundId(refund.get("refund_id"));
                }
            }
            bossOrderService.updateRefundInfo(bossOrder);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private Map<String, String> refundParams(BossOrder bossOrder) {
        Map<String, String> params = new HashMap<>();
        params.put("sign_type", "HMAC-SHA256");
        params.put("transaction_id", bossOrder.getTransactionId());
        String refundOrderNo = getOrderNo();
        bossOrder.setRefundOrderNo(refundOrderNo);
        params.put("out_refund_no", refundOrderNo);
        params.put("total_fee", getAmount(bossOrder.getActualAmount()));
        params.put("refund_fee", getAmount(bossOrder.getRefundAmount()));
        params.put("notify_url", "http://www.shengongying.cn:18801/boss/pay/callback/wechat/refund");
        return params;
    }

    private void verifyPrepayResult(Map<String, String> prepay) throws BusinessException {
        if (!prepay.get(returnCode)
                .equalsIgnoreCase("SUCCESS") || !prepay.get(resultCode)
                .equalsIgnoreCase("SUCCESS")) {
            String ec = prepay.get(errCode);
            String ecs = prepay.get(errCodeDes);
            throw new BusinessException("预下单失败:{" + ec + "+,+" + ecs + "}");
        }
    }

    private Map<String, String> createParams(BossOrder bossOrder) {
        Map<String, String> params = new HashMap<>();
        params.put("sign_type", "HMAC-SHA256");
        params.put("body", "神工营-招聘");
        params.put("attach", getAttach());
        String orderNo = getOrderNo();
        params.put("out_trade_no", orderNo);
        bossOrder.setOrderNo(orderNo);
        params.put("total_fee", getAmount(bossOrder.getActualAmount()));
        params.put("spbill_create_ip", getIp());
        params.put("notify_url", "http://www.shengongying.cn:18801/boss/pay/callback/wechat");
        params.put("trade_type", "APP");
        return params;
    }

    private String getAmount(BigDecimal amount) {
        return amount
                .multiply(BigDecimal.valueOf(100))
                .setScale(0, RoundingMode.DOWN)
                .toString();
    }

    private String getAttach() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("type", 1);
        return jsonObject.toString();
    }

    private String getOrderNo() {
        return UUID.randomUUID()
                .toString()
                .replace("-", "");
    }

    private String getIp() {
        try {
            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
            InetAddress ip;
            while (networkInterfaces.hasMoreElements()) {
                NetworkInterface netInterface = networkInterfaces.nextElement();
                Enumeration<InetAddress> inetAddresses = netInterface.getInetAddresses();
                while (inetAddresses.hasMoreElements()) {
                    ip = inetAddresses.nextElement();
                    if (ip instanceof Inet4Address && !ip.isLinkLocalAddress() && !ip.isLoopbackAddress()) {
                        return ip.getHostAddress();
                    }
                }
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return "47.110.40.66";
    }
}
